/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.nacos.client.config;

import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
import com.alibaba.nacos.client.config.filter.impl.ConfigRequest;
import com.alibaba.nacos.client.config.filter.impl.ConfigResponse;
import com.alibaba.nacos.client.config.http.ServerHttpAgent;
import com.alibaba.nacos.client.config.impl.ClientWorker;
import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor;
import com.alibaba.nacos.client.config.impl.ServerListManager;
import com.alibaba.nacos.client.config.utils.ContentUtils;
import com.alibaba.nacos.client.config.utils.ParamUtils;
import com.alibaba.nacos.client.utils.LogUtils;
import com.alibaba.nacos.client.utils.ParamUtil;
import com.alibaba.nacos.client.utils.ValidatorUtils;
import org.slf4j.Logger;

import java.util.Arrays;
import java.util.Properties;

/**
 * Config Impl.
 *
 * @author Nacos
 */
@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
public class NacosConfigService implements ConfigService {

  private static final Logger LOGGER = LogUtils.logger(NacosConfigService.class);

  /**
   * will be deleted in 2.0 later versions
   */
  @Deprecated
  ServerHttpAgent agent = null;

  /**
   * long polling.
   */
  private final ClientWorker worker;

  private String namespace;

  private final ConfigFilterChainManager configFilterChainManager = new ConfigFilterChainManager();

  /**
   * 配置中心核心类
   */
  public NacosConfigService(Properties properties) throws NacosException {
    ValidatorUtils.checkInitParam(properties);

    initNamespace(properties);
    ServerListManager serverListManager = new ServerListManager(properties);
    //获取nacos server的列表，如果如果变更则发布一个服务列表变更事件
    serverListManager.start();
    //初始化客户端工作者
    this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, properties);
    // will be deleted in 2.0 later versions
    agent = new ServerHttpAgent(serverListManager);

  }

  private void initNamespace(Properties properties) {
    namespace = ParamUtil.parseNamespace(properties);
    properties.put(PropertyKeyConst.NAMESPACE, namespace);
  }

  @Override
  public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
    return getConfigInner(namespace, dataId, group, timeoutMs);
  }

  @Override
  public String getConfigAndSignListener(String dataId, String group, long timeoutMs,
      Listener listener)
      throws NacosException {
    String content = getConfig(dataId, group, timeoutMs);
    worker.addTenantListenersWithContent(dataId, group, content, Arrays.asList(listener));
    return content;
  }

  @Override
  public void addListener(String dataId, String group, Listener listener) throws NacosException {
    worker.addTenantListeners(dataId, group, Arrays.asList(listener));
  }

  @Override
  public boolean publishConfig(String dataId, String group, String content) throws NacosException {
    return publishConfig(dataId, group, content, ConfigType.getDefaultType().getType());
  }

  @Override
  public boolean publishConfig(String dataId, String group, String content, String type)
      throws NacosException {
    return publishConfigInner(namespace, dataId, group, null, null, null, content, type, null);
  }

  @Override
  public boolean publishConfigCas(String dataId, String group, String content, String casMd5)
      throws NacosException {
    return publishConfigInner(namespace, dataId, group, null, null, null, content,
        ConfigType.getDefaultType().getType(), casMd5);
  }

  @Override
  public boolean publishConfigCas(String dataId, String group, String content, String casMd5,
      String type)
      throws NacosException {
    return publishConfigInner(namespace, dataId, group, null, null, null, content, type, casMd5);
  }

  @Override
  public boolean removeConfig(String dataId, String group) throws NacosException {
    return removeConfigInner(namespace, dataId, group, null);
  }

  @Override
  public void removeListener(String dataId, String group, Listener listener) {
    worker.removeTenantListener(dataId, group, listener);
  }

  private String getConfigInner(String tenant, String dataId, String group, long timeoutMs)
      throws NacosException {
    group = null2defaultGroup(group);
    ParamUtils.checkKeyParam(dataId, group);
    ConfigResponse cr = new ConfigResponse();

    cr.setDataId(dataId);
    cr.setTenant(tenant);
    cr.setGroup(group);

    // 优先使用本地配置
    String content = LocalConfigInfoProcessor
        .getFailover(worker.getAgentName(), dataId, group, tenant);
    if (content != null) {
      LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}",
          worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
      cr.setContent(content);
      configFilterChainManager.doFilter(null, cr);
      content = cr.getContent();
      return content;
    }

    try {
      String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
      cr.setContent(ct[0]);

      configFilterChainManager.doFilter(null, cr);
      content = cr.getContent();

      return content;
    } catch (NacosException ioe) {
      if (NacosException.NO_RIGHT == ioe.getErrCode()) {
        throw ioe;
      }
      LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
          worker.getAgentName(), dataId, group, tenant, ioe.toString());
    }

    LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}",
        worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
    //加载快照
    content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), dataId, group, tenant);
    cr.setContent(content);
    configFilterChainManager.doFilter(null, cr);
    content = cr.getContent();
    return content;
  }

  private String null2defaultGroup(String group) {
    return (null == group) ? Constants.DEFAULT_GROUP : group.trim();
  }

  private boolean removeConfigInner(String tenant, String dataId, String group, String tag)
      throws NacosException {
    group = null2defaultGroup(group);
    ParamUtils.checkKeyParam(dataId, group);
    return worker.removeConfig(dataId, group, tenant, tag);
  }

  private boolean publishConfigInner(String tenant, String dataId, String group, String tag,
      String appName,
      String betaIps, String content, String type, String casMd5) throws NacosException {
    group = null2defaultGroup(group);
    ParamUtils.checkParam(dataId, group, content);

    ConfigRequest cr = new ConfigRequest();
    cr.setDataId(dataId);
    cr.setTenant(tenant);
    cr.setGroup(group);
    cr.setContent(content);
    cr.setType(type);
    configFilterChainManager.doFilter(cr, null);
    content = cr.getContent();

    return worker.publishConfig(dataId, group, tenant, appName, tag, betaIps, content, casMd5);

  }

  @Override
  public String getServerStatus() {
    if (worker.isHealthServer()) {
      return "UP";
    } else {
      return "DOWN";
    }
  }

  @Override
  public void shutDown() throws NacosException {
    worker.shutdown();
  }
}
